/**
 * 
 */
package cz.geoserver.KMLpublisher.util;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cz.geoserver.KMLpublisher.db.model.BBOX;
import cz.geoserver.KMLpublisher.db.model.ClusterInfo;
import cz.geoserver.KMLpublisher.db.model.GeometryFeature;

/**
 * Class processes incoming request and according to combination of parameters calls other classes
 * @author mkepka
 *
 */
public class ServletUtil {

    /**
     * Arrays with possible forms of parameters 
     */
    private static String[] parBBOX = {"BBOX", "bbox", "Bbox"};
    //private static String[] parVIEW = {"VIEW", "view", "View"};
    private static String[] parQuery = {"QUERY", "query", "Query"};
    private static String[] parSqlId = {"SQLID","sqlId", "SQL_ID", "sql_id", "SqlId"};
    private static String[] parDataTable = {"DATA_TABLE", "data_table", "Data_Table", "Data_table", "data_Table"};
    private static String[] parClusteredAtt = {"CLUSTERED_ATT", "clustered_att", "Clustered_Att", "Clustered_att", "clustered_Att"};
    private static String[] parRequest = {"REQUEST", "request"};
    private static String[] parService = {"SERVICE", "service"};
    private static String[] parVersion = {"VERSION", "version"};
    
    private static GeomFeatureUtil gfUtil;
    private static ClusterUtil cUtil;
    private static KMLwriter kmlWriter;
    
    private static String kmlContentType = "application/vnd.google-earth.kml+xml";
    private static int kmlStatus = 200;
    
    private static boolean fromServlet;
    
    /**
     * Constructor creates instance of class and creates instances of other Util classes 
     * @param fromServlet - Boolean is true if app runs as web-app and class is called from servlet, 
     * false if app runs as Geoserver plugin 
     */
    public ServletUtil(boolean fromServlet){
        gfUtil = new GeomFeatureUtil();
        cUtil = new ClusterUtil();
        kmlWriter = new KMLwriter(fromServlet);
        this.fromServlet=fromServlet;
    }

    /**
     * Method calls other methods from utilities according to combination of input parameters in request. 
     * @param req - HttpServletRequest request with input parameters
     * @param resp - HttpServletResponse response where to sent response
     * @throws SQLException 
     * @throws IOException
     */
    public void processRequest(HttpServletRequest req, HttpServletResponse resp) throws SQLException, IOException{
        Map paramMap = req.getParameterMap();
        // Finding of possible parameters in GET request and their current forms.
        String queryPar = findParameterName(parQuery, paramMap);
        String bboxPar = findParameterName(parBBOX, paramMap);
        String sqlIdPar = findParameterName(parSqlId, paramMap);
        String dataTabPar = findParameterName(parDataTable, paramMap);
        String clustAttPar = findParameterName(parClusteredAtt, paramMap);
        
        // Preparing of outputs Writer for servlet, Stream for Geoserver plugin
        PrintWriter pw = null;
        ServletOutputStream out = null;
        if(fromServlet){
            pw = resp.getWriter();
        }
        else{
            out = resp.getOutputStream();	
        }
        
        // Setting form of response content
        resp.setContentType(kmlContentType);
        resp.setStatus(kmlStatus);
        
        // Solving combination of input parameters
        // If there is parameter QUERY in request - either SQL will be stored or data will be loaded.
        if(!queryPar.isEmpty()){
            String query = req.getParameter(queryPar);
            
            // if there are query, data_table and clustered_att parameters in request used in hierarchy
            if(!dataTabPar.isEmpty() && !clustAttPar.isEmpty()){
                String dataTab = req.getParameter(dataTabPar);
                String clustAtt = req.getParameter(clustAttPar);
                ClusterInfo cluster = cUtil.getClustersInfo(dataTab, clustAtt);
                SQLparser sql = new SQLparser(query, cluster);
                
                // If there is BBOX parameter data can be loaded and can be returned directly.
                if(!bboxPar.isEmpty()){
                    // compute level of hierarchy, then load data from DB and send KML back to client
                    BBOX bbox = new BBOX(req.getParameter(bboxPar));
                    int level = cUtil.computeLevel(cluster, bbox);
                    List<GeometryFeature> gfList = gfUtil.getGeomFeaturesByQuery(sql.getSqlWhierarchyAbbox(level, bbox));
                    // Method of sending depends on where is this method calls from.
                    if(fromServlet){
                        kmlWriter.sendStaticKML(pw, gfList);
                    }
                    else{
                        kmlWriter.sendStaticKMLAsStream(out, gfList);
                    }
                }
                // If there is not BBOX parameter query will be stored and KML with Network link will be returned.
                else{
                    // store query under id key
                    String sqlId = "";
                    // If this method is called from Servlet id will be session id
                    if(fromServlet){
                        sqlId = req.getSession().getId();
                        QueryMapHolder.getInstance().putSQLparser(sqlId, sql);
                        kmlWriter.sendKMLNetworklink(pw, sqlId, createRequestURL(req), sql);
                    }
                    // If this method is called from plugin id will be requestedSessionId or current time in milis
                    else{
                        sqlId = req.getRequestedSessionId();
                        if(sqlId != null){
                            QueryMapHolder.getInstance().putSQLparser(sqlId, sql);
                        }
                        else{
                            sqlId = QueryMapHolder.getInstance().putSQLparser(sql);
                        }
                        kmlWriter.sendKMLNetworklinkAsStream(out, sqlId, createRequestURL(req), sql);
                    }
                }
            }
            // if there are not any parameters used in hierarchy mechanism
            else {
                SQLparser sql = new SQLparser(query);
             // If there is BBOX parameter data can be loaded and can be returned directly.
                if(!bboxPar.isEmpty()){
                    // load data from DB and send static KML to client
                    BBOX bbox = new BBOX(req.getParameter(bboxPar));
                    List<GeometryFeature> gfList = gfUtil.getGeomFeaturesByQuery(sql.getSqlWbbox(bbox));
                 // Method of sending depends on where is this method calls from.
                    if(fromServlet){
                        kmlWriter.sendStaticKML(pw, gfList);
                    }
                    else{
                        kmlWriter.sendStaticKMLAsStream(out, gfList);
                    }
               }
             // If there is not BBOX parameter query will be stored and KML with Network link will be returned.
               else{
                   // save sql and return KML with Networklink
                   String sesId="";
                // If this method is called from Servlet id will be session id
                   if(fromServlet){
                       sesId = req.getSession().getId();
                       QueryMapHolder.getInstance().putSQLparser(sesId, sql);
                       kmlWriter.sendKMLNetworklink(pw, sesId, createRequestURL(req), sql);
                   }
                // If this method is called from plugin id will be requestedSessionId or current time in milis
                    else{
                        sesId = req.getRequestedSessionId();
                        if(sesId != null){
                            QueryMapHolder.getInstance().putSQLparser(sesId, sql);
                       }
                        else{
                            sesId = QueryMapHolder.getInstance().putSQLparser(sql);
                       }
                       kmlWriter.sendKMLNetworklinkAsStream(out, sesId, createRequestURL(req), sql);
                   }
               }
            }
        }
        // If there are parameters SQLID and BBOX in request - data will be loaded. 
        else if(!sqlIdPar.isEmpty() && !bboxPar.isEmpty()){
            // load SQL from Map, load data and send static KML to client
            String sqlId = req.getParameter(sqlIdPar);
            SQLparser sql = QueryMapHolder.getInstance().getSQLparser(sqlId);
            // if there was found some SQL for this given ID 
            if(sql!= null){
                BBOX bbox = new BBOX(req.getParameter(bboxPar));
                List<GeometryFeature> gfList;
                
                // if there are parameters for using hierarchy mechanism in SQLparser load data from hierarchy
                if(sql.isSetClusterInfo()){
                    int level = cUtil.computeLevel(sql.getClusterInfo(), bbox);
                    gfList = gfUtil.getGeomFeaturesByQuery(sql.getSqlWhierarchyAbbox(level, bbox));
                }
                // load data without hierarchy
                else{
                    gfList = gfUtil.getGeomFeaturesByQuery(sql.getSqlWbbox(bbox));
                }
             // Method of sending depends on where is this method calls from.
                if(fromServlet){
                    kmlWriter.sendStaticKML(pw, gfList);
                }
                else{
                    kmlWriter.sendStaticKMLAsStream(out, gfList);
                }
            }
         // if there was not found any SQL for this ID
            else{
                throw new SQLException("NO SQL query was found below this ID!");
            }
        }
        // If there is not any usable combination of parameters in request.
        else{
            throw new SQLException("Request does not contain required combination of parameters!");
        }
    }

    /**
     * Method for finding the parameter name in String expression,
     * parameter name can be case insensitive, but must be specified potential forms in array
     * @param possibleNames Array with potential forms of parameter name
     * @param expression String expression where should be one form of parameter
     * @return Return particular parameter name from request or empty String if there is no such parameter name
     */
    public String findParameterName(String[] possibleNames, String expression) {
        String paramName = "";
        for(int i = 0; i<possibleNames.length; i++){
            boolean contain = expression.contains(possibleNames[i]);
            if(contain){
                paramName = possibleNames[i];
            }
        }
        return paramName;
    }

    /**
     * Method for finding the parameter name in request, 
     * parameter name can be case insensitive, but must be specified potential forms in array 
     * @param possibleNames Array with potential forms of parameter name
     * @param parameterMap Map with request parameters 
     * @return Return particular parameter name from request or empty String if there is no such parameter name
     */
    public String findParameterName(String[] possibleNames, Map parameterMap){
        String name = "";
        for(int i = 0; i<possibleNames.length; i++){
            boolean isEqual = parameterMap.containsKey(possibleNames[i]);
            if(isEqual){
                name = possibleNames[i];
            }
        }
        return name;
    }

    /**
     * Method creates part of URL where to send requests on data.
     * 
     * @param req - HttpServletRequest with input parameters 
     * @return part of URL where to send requests on data as String
     */
    private String createRequestURL(HttpServletRequest req){
        if(fromServlet){
            return req.getRequestURL()+"?";
        }
        else{
            Map paramMap = req.getParameterMap();
            String requestPar = findParameterName(parRequest, paramMap);
            String servicePar = findParameterName(parService, paramMap);
            String versionPar = findParameterName(parVersion, paramMap);
            
            String request = requestPar+"="+req.getParameter(requestPar);
            String service = servicePar+"="+ req.getParameter(servicePar);
            String version = versionPar+"="+req.getParameter(versionPar);
            
            String fullReqURL = req.getRequestURL()+"?"+request+"&amp;"+service+"&amp;"+version+"&amp;";
            
            return fullReqURL;
        }
    }
}